/*
 * Copyright (C) 2015-2019 Alibaba Group Holding Limited
 */

#include "aos/kernel.h"
#include "aos/list.h"
#include "ulog/ulog.h"
#include "aos/hal/gpio.h"

#include "soc_init.h"

#define TAG "GPIO"

typedef struct
{
    slist_t next;
    uint8_t port;
    uint8_t actual_value;    /*used for irq*/
    uint8_t config;
    uint8_t irq_mode;
    drv_manufacturer_t drv_info;
    const st_gpio_opt_t *opt;
} hal_gpio_t;

static slist_t g_st_gpio_info = {0};
static uint32_t    g_u32gpiocount = 0;
static aos_mutex_t g_st_gpioinfo_mutex = {0}; 

static void add_gpio_info(hal_gpio_t *pstinfo)
{
    aos_mutex_lock(&g_st_gpioinfo_mutex, AOS_WAIT_FOREVER);
    slist_add_tail(&pstinfo->next, &g_st_gpio_info);
    aos_mutex_unlock(&g_st_gpioinfo_mutex);
}

static void remove_gpio_info(hal_gpio_t *pstinfo)
{
    aos_mutex_lock(&g_st_gpioinfo_mutex, AOS_WAIT_FOREVER);
    slist_del(&pstinfo->next, &g_st_gpio_info);
    aos_mutex_unlock(&g_st_gpioinfo_mutex);
}

static hal_gpio_t* get_gpio_info_by_port(uint8_t port)
{
    hal_gpio_t *pst_gpioinfo = NULL;
    slist_t    *cur = NULL;

    aos_mutex_lock(&g_st_gpioinfo_mutex, AOS_WAIT_FOREVER);
    slist_for_each_entry_safe(&g_st_gpio_info, cur, pst_gpioinfo, hal_gpio_t, next) {
        if (pst_gpioinfo->port == port) {
            break;
        }
    }
    aos_mutex_unlock(&g_st_gpioinfo_mutex);

    return pst_gpioinfo;

}

int32_t hal_gpio_init(gpio_dev_t *gpio)
{
    int32_t ret = 0;
    hal_gpio_t *pst_gpio_info = NULL;
    gpio_mapping_t *pst_drv_info = NULL;
    const st_gpio_opt_t  *pst_gpio_opt = NULL;

    if (NULL == gpio) {
        LOGE(TAG, "%s Invalid input", __func__);
        return -1;
    }

    if (aos_mutex_is_valid(&g_st_gpioinfo_mutex) == 0) {
        ret = aos_mutex_new(&g_st_gpioinfo_mutex);
        if (ret) {
            LOGE(TAG, "%s fail to new gpio mutex", __func__);
            return -1;
        }
    }

    pst_drv_info = get_drv_manu_info(gpio);
    if (NULL == pst_drv_info) {
        LOGE(TAG, "fail to get gpio %d drv info ", gpio->port);
        return -1;
    }

    pst_gpio_opt = get_gpio_opt(gpio);
    if (NULL == pst_gpio_opt) {
        LOGE(TAG, "fail to get gpio %d opt at %s", gpio->port, __func__);
        return -1;
    }

    if (pst_gpio_opt->init == NULL) {
        LOGE(TAG, "gpio %d init opt is NULL", gpio->port);
        return -1;
    }
    
    pst_gpio_info = get_gpio_info_by_port(gpio->port);
    if (NULL != pst_gpio_info) {
        LOGE(TAG, "%s gpio have already inited", __func__, gpio->port);
        return -1;
    } else {
        pst_gpio_info = aos_malloc(sizeof(hal_gpio_t));
        if (NULL == pst_gpio_info) {
            LOGE(TAG, "%s fail to malloc %d", __func__, sizeof(hal_gpio_t));
            return -1;
        }
        memset(pst_gpio_info, 0, sizeof(hal_gpio_t));
    }

    ret = pst_gpio_opt->init(&(pst_drv_info->drv), gpio->config);
    if (ret) {
        LOGE(TAG, "gpio %d drv base %p pin %d config %d init fail ret %d", gpio->port,
            pst_drv_info->drv.base, pst_drv_info->drv.pin, gpio->config, ret);
        aos_free(pst_gpio_info);
        return -1;
    }
    pst_gpio_info->opt = pst_gpio_opt;
    pst_gpio_info->port = gpio->port;
    pst_gpio_info->config = gpio->config;
    memcpy(&pst_gpio_info->drv_info, &pst_drv_info->drv, sizeof(drv_manufacturer_t));

    /*add gpio info*/
    add_gpio_info(pst_gpio_info);
    return 0;
}

int32_t hal_gpio_output_high(gpio_dev_t *gpio)
{
    int32_t ret = 0;
    hal_gpio_t *pst_gpioinfo = NULL;

    if (gpio == NULL) {
        LOGE(TAG, "%s Invalid input", __func__);
        return -1;
    }

    pst_gpioinfo = get_gpio_info_by_port(gpio->port); 
    if (NULL == pst_gpioinfo) {
        LOGE(TAG, "gpio %d output high for haven't init yet", gpio->port);
        return -1;
    }

    if (pst_gpioinfo->opt->output_high == NULL) {
        LOGE(TAG, "gpio %d output high opt is NULL", gpio->port);
        return -1;
    }

    ret = pst_gpioinfo->opt->output_high(&pst_gpioinfo->drv_info);
    if (ret) {
        LOGE(TAG, "gpio port %d drv info %p %d output high %p fail", gpio->port, 
               pst_gpioinfo->drv_info.base, pst_gpioinfo->drv_info.pin, pst_gpioinfo->opt->output_high);
        return -1;
    }

    return 0;
}

int32_t hal_gpio_output_low(gpio_dev_t *gpio)
{
    int32_t ret = 0;
    hal_gpio_t *pst_gpioinfo = NULL;

    if (gpio == NULL) {
        LOGE(TAG, "%s Invalid input", __func__);
        return -1;
    }

    pst_gpioinfo = get_gpio_info_by_port(gpio->port); 
    if (NULL == pst_gpioinfo) {
        LOGE(TAG, "gpio %d output high for haven't init yet", gpio->port);
        return -1;
    }

    if (pst_gpioinfo->opt->output_low == NULL) {
        LOGE(TAG, "gpio %d output high opt is NULL", gpio->port);
        return -1;
    }

    ret = pst_gpioinfo->opt->output_low(&pst_gpioinfo->drv_info);
    if (ret) {
        LOGE(TAG, "gpio port %d drv info %p %d output low %p fail", gpio->port, 
               pst_gpioinfo->drv_info.base, pst_gpioinfo->drv_info.pin, pst_gpioinfo->opt->output_low);
        return -1;
    }

    return 0;
}

int32_t hal_gpio_output_toggle(gpio_dev_t *gpio)
{
    int32_t ret = 0;
    hal_gpio_t *pst_gpioinfo = NULL;

    if (gpio == NULL) {
        LOGE(TAG, "%s Invalid input", __func__);
        return -1;
    }

    pst_gpioinfo = get_gpio_info_by_port(gpio->port); 
    if (NULL == pst_gpioinfo) {
        LOGE(TAG, "gpio %d output toggle for haven't init yet", gpio->port);
        return -1;
    }

    if (pst_gpioinfo->opt->output_toggle == NULL) {
        LOGE(TAG, "gpio %d output toggle opt is NULL", gpio->port);
        return -1;
    }

    ret = pst_gpioinfo->opt->output_toggle(&pst_gpioinfo->drv_info);
    if (ret) {
        LOGE(TAG, "gpio port %d drv info %p %d output toggle %p fail", gpio->port, 
               pst_gpioinfo->drv_info.base, pst_gpioinfo->drv_info.pin, pst_gpioinfo->opt->output_toggle);
        return -1;
    }

    return 0;
}

int32_t hal_gpio_input_get(gpio_dev_t *gpio, uint32_t *value)
{
    int32_t ret = 0;
    uint32_t data = 0;
    hal_gpio_t *pst_gpioinfo = NULL;

    if (gpio == NULL || NULL == value) {
        LOGE(TAG, "%s Invalid input", __func__);
        return -1;
    }

    pst_gpioinfo = get_gpio_info_by_port(gpio->port); 
    if (NULL == pst_gpioinfo) {
        LOGE(TAG, "gpio %d input get for haven't init yet", gpio->port);
        return -1;
    }

    if (pst_gpioinfo->opt->input_get == NULL) {
        LOGE(TAG, "gpio %d output toggle opt is NULL", gpio->port);
        return -1;
    }

    ret = pst_gpioinfo->opt->input_get(&pst_gpioinfo->drv_info, &data);
    if (ret) {
        LOGE(TAG, "gpio port %d drv info %p %d input get %p fail", gpio->port, 
               pst_gpioinfo->drv_info.base, pst_gpioinfo->drv_info.pin, pst_gpioinfo->opt->input_get);
        return -1;
    }
		
    *value = data;
    return 0;
}

int32_t hal_gpio_enable_irq(gpio_dev_t *gpio, gpio_irq_trigger_t trigger,
                            gpio_irq_handler_t handler, void *arg)
{
    int32_t ret = 0;
    hal_gpio_t *pst_gpioinfo = NULL;

    if (NULL == gpio || handler == NULL) {
        LOGE(TAG, "Invalid input %s", __func__);
        return -1;
    }

    pst_gpioinfo = get_gpio_info_by_port(gpio->port); 
    if (NULL == pst_gpioinfo) {
        LOGE(TAG, "gpio %d enable irq fail for haven't init yet", gpio->port);
        return -1;
    }

    if (pst_gpioinfo->opt->enable_irq == NULL) {
        LOGE(TAG, "gpio %d enable irq faile opt is NULL", gpio->port);
        return -1;
    }

    ret = pst_gpioinfo->opt->enable_irq(&pst_gpioinfo->drv_info, trigger, handler, arg);
    if (ret) {
        LOGE(TAG, "gpio port %d drv info %p %d enable irq %p fail", gpio->port, 
               pst_gpioinfo->drv_info.base, pst_gpioinfo->drv_info.pin, pst_gpioinfo->opt->enable_irq);
        return -1;
    }

    return 0;

}

int32_t hal_gpio_disable_irq(gpio_dev_t *gpio)
{
    int32_t ret = 0;
    hal_gpio_t *pst_gpioinfo = NULL;

    if (NULL == gpio) {
        LOGE(TAG, "Invalid input %s", __func__);
        return -1;
    }

    pst_gpioinfo = get_gpio_info_by_port(gpio->port); 
    if (NULL == pst_gpioinfo) {
        LOGE(TAG, "gpio %d disable irq fail for haven't init yet", gpio->port);
        return -1;
    }

    if (pst_gpioinfo->opt->disable_irq == NULL) {
        LOGE(TAG, "gpio %d disable irq faile opt is NULL", gpio->port);
        return -1;
    }

    ret = pst_gpioinfo->opt->disable_irq(&pst_gpioinfo->drv_info);
    if (ret) {
        LOGE(TAG, "gpio port %d drv info %p %d disable irq %p fail", gpio->port, 
               pst_gpioinfo->drv_info.base, pst_gpioinfo->drv_info.pin, pst_gpioinfo->opt->disable_irq);
        return -1;
    }

    return 0;
}

int32_t hal_gpio_clear_irq(gpio_dev_t *gpio)
{
    int32_t ret = 0;
    hal_gpio_t *pst_gpioinfo = NULL;

    if (NULL == gpio) {
        LOGE(TAG, "Invalid input %s", __func__);
        return -1;
    }

    pst_gpioinfo = get_gpio_info_by_port(gpio->port); 
    if (NULL == pst_gpioinfo) {
        LOGE(TAG, "gpio %d clear irq fail for haven't init yet", gpio->port);
        return -1;
    }

    if (pst_gpioinfo->opt->clear_irq == NULL) {
        LOGE(TAG, "gpio %d clear irq faile opt is NULL", gpio->port);
        return -1;
    }

    ret = pst_gpioinfo->opt->clear_irq(&pst_gpioinfo->drv_info);
    if (ret) {
        LOGE(TAG, "gpio port %d drv info %p %d clear irq %p fail", gpio->port, 
               pst_gpioinfo->drv_info.base, pst_gpioinfo->drv_info.pin, pst_gpioinfo->opt->clear_irq);
        return -1;
    }

    return 0;
}

int32_t hal_gpio_finalize(gpio_dev_t *gpio)
{
    int32_t ret = 0;
    hal_gpio_t *pst_gpioinfo = NULL;

    if (NULL == gpio) {
        LOGE(TAG, "Invalid input %s", __func__);
        return -1;
    }

    pst_gpioinfo = get_gpio_info_by_port(gpio->port); 
    if (NULL == pst_gpioinfo) {
        LOGI(TAG, "gpio %d finalize fail for haven't init yet", gpio->port);
        return -1;
    }

    if (pst_gpioinfo->opt->finalize == NULL) {
        LOGE(TAG, "gpio %d clear irq faile opt is NULL", gpio->port);
    } else {
        ret = pst_gpioinfo->opt->finalize(&pst_gpioinfo->drv_info);
        if (ret) {
            LOGE(TAG, "gpio port %d drv info %p %d finalize %p fail", gpio->port, 
                pst_gpioinfo->drv_info.base, pst_gpioinfo->drv_info.pin, pst_gpioinfo->opt->finalize);
        }
    }

    remove_gpio_info(pst_gpioinfo);

    return 0;
}
